﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Jurassic;
using Flower.Directory.Scripting;
using Jurassic.Library;
using Flower.Logging;

namespace Flower.Directory.Host.Default
{
    public class ScriptEnginePool : IScriptEnginePool
    {
        private static readonly Log Log = LogManager.CreateLog(typeof(ScriptEnginePool).Name);

        private static readonly TimeSpan EngineGettingTimeout = TimeSpan.FromSeconds(10);

        private static readonly Lazy<List<string>> GlobalDefaultProperties;

        private readonly int _maxSize;
        private readonly ConcurrentDictionary<string, BlockingCollection<ScriptEngine>> _pools = 
            new ConcurrentDictionary<string,BlockingCollection<ScriptEngine>>();

        static ScriptEnginePool()
        {
            GlobalDefaultProperties = new Lazy<List<string>>
            (
                () => 
                {
                    ScriptEngine scriptEngine = Setup.CreateEngine();
                    return
                        scriptEngine
                            .Global
                            .Properties
                            .Select(p => p.Name)
                            .ToList();
                }
            );
        }

        public ScriptEnginePool(int maxSize)
        {
            _maxSize = maxSize;
        }

        public ScriptEngine GetScriptEngine(string userName)
        {
            BlockingCollection<ScriptEngine> pool =
                _pools.GetOrAdd
                (
                    userName,
                    un =>
                    {
                        var coll = new BlockingCollection<ScriptEngine>(_maxSize);

                        Task.Factory.StartNew
                        (
                            () =>
                            {
                                try
                                {
                                    for (int i = 0; i < _maxSize; i++)
                                    {
                                        coll.Add(Setup.CreateEngine());
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Log.Error("Failed to create script engine.", ex);
                                }
                            }
                        );

                        return coll;
                    }
                );

            ScriptEngine engine;

            if (!pool.TryTake(out engine, EngineGettingTimeout))
            {
                throw new TimeoutException("Failed to obtain script engine.");
            }

            return engine;
        }

        public void ReturnScriptEngine(string userName, ScriptEngine scriptEngine)
        {
            BlockingCollection<ScriptEngine> pool;

            if (_pools.TryGetValue(userName, out pool))
            {
                IEnumerable<string> createdGlobalProperties =
                        scriptEngine
                            .Global
                            .Properties
                            .Where(p => !GlobalDefaultProperties.Value.Contains(p.Name))
                            .Select(p => p.Name);

                foreach (string propName in createdGlobalProperties)
                {
                    scriptEngine.Global.Delete(propName, false);
                }

                pool.TryAdd(scriptEngine);
            }
        }
    }
}
